package com.jackstraw.jack_base.datasource.elasticsearch.config;

import com.jackstraw.jack_base.datasource.common.config.DataSourceConfiguration;
import com.jackstraw.jack_base.datasource.elasticsearch.entity.bean.ElasticSearchFactory;
import com.jackstraw.jack_base.datasource.elasticsearch.entity.bean.ElasticSearchProperties;
import com.jackstraw.jack_base.datasource.elasticsearch.entity.bean.ElasticSearchPropertiesEntity;
import com.jackstraw.jack_base.datasource.elasticsearch.entity.bean.ElasticSearchRouting;
import com.jackstraw.jack_base.datasource.elasticsearch.entity.bean.ElasticSearchTemplate;
import com.jackstraw.jack_base.datasource.elasticsearch.entity.bean.ElasticSearchTemplateCut;
import com.jackstraw.jack_base.datasource.elasticsearch.exception.BadElasticSearchException;
import com.jackstraw.jack_base.util.ObjectUtil;
import com.jackstraw.jack_base.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.env.Environment;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.config.AbstractReactiveElasticsearchConfiguration;
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * elasticsearch主配置
 */
@Slf4j
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true,proxyTargetClass = true)
@EnableAutoConfiguration(exclude = {ElasticsearchRestClientAutoConfiguration.class, ReactiveElasticsearchRestClientAutoConfiguration.class})
public class ElasticSearchConfiguration
        extends DataSourceConfiguration<ElasticSearchRouting,
        ElasticSearchPropertiesEntity,
        ElasticSearchTemplate,
        ElasticSearchFactory,
        ElasticSearchTemplateCut,
        ElasticSearchProperties,
        ElasticSearchConfiguration> {

    public ElasticSearchConfiguration(ApplicationContext applicationContext, Environment environment) {
        super(applicationContext, environment);
    }

    /**
     * Register custom {@link Converter}s in a {@link ElasticsearchCustomConversions} object if required.
     *
     * @return never {@literal null}.
     */
    @Bean
    public ElasticsearchCustomConversions elasticsearchCustomConversions() {
        return new ElasticsearchCustomConversions(Collections.emptyList());
    }


    /**
     * Creates a {@link SimpleElasticsearchMappingContext} equipped with entity classes scanned from the mapping base
     * package.
     *
     * @return never {@literal null}.
     * @see #getMappingBasePackages()
     */
    @Bean
    @ConditionalOnBean(value = {ElasticsearchCustomConversions.class})
    public SimpleElasticsearchMappingContext elasticsearchMappingContext(ElasticsearchCustomConversions elasticsearchCustomConversions) {
        SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
        mappingContext.setInitialEntitySet(getInitialEntitySet());
        mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions.getSimpleTypeHolder());

        return mappingContext;
    }


    @Bean
    @ConditionalOnBean(value = {ElasticsearchCustomConversions.class, SimpleElasticsearchMappingContext.class})
    public ElasticsearchConverter elasticsearchConverter(
            SimpleElasticsearchMappingContext elasticsearchMappingContext,
            ElasticsearchCustomConversions elasticsearchCustomConversions) {
        MappingElasticsearchConverter elasticsearchConverter = new MappingElasticsearchConverter(
                elasticsearchMappingContext);
        elasticsearchConverter.setConversions(elasticsearchCustomConversions);
        return elasticsearchConverter;
    }

    @Override
    @Bean("elasticSearchProperties")
    @ConfigurationProperties(prefix = "jack.datasource.elasticsearch")
    public ElasticSearchProperties properties() {
        return new ElasticSearchProperties();
    }


    @Override
    @Bean("elasticSearchBuildFactory")
    public ElasticSearchFactory dataSourceBuildFactory() {
        return new ElasticSearchFactory();
    }


    @Override
    @Bean("elasticSearchTemplateCut")
    @ConditionalOnBean(name = {"elasticSearchBuildFactory"})
    public ElasticSearchTemplateCut dataSourceTemplateCut(ElasticSearchFactory dataSourceBuildFactory) {
        return new ElasticSearchTemplateCut(dataSourceBuildFactory);
    }


    @Override
    @Bean("elasticSearchRouting")
    @ConditionalOnBean(name = {"elasticSearchProperties", "elasticSearchBuildFactory", "elasticSearchTemplateCut"})
    public ElasticSearchRouting routing() {

        ElasticSearchProperties properties = SpringUtil.getBean("elasticSearchProperties", ElasticSearchProperties.class);
        if (ObjectUtil.ObjectUtilByLang3.isEmpty(properties)) {
            log.error("elasticSearch配置文件异常");
            throw new BadElasticSearchException("037", "elasticSearch配置文件异常");
        }

        ElasticSearchTemplateCut dataSourceTemplateCut = SpringUtil.getBean("elasticSearchTemplateCut", ElasticSearchTemplateCut.class);
        if (ObjectUtil.ObjectUtilByLang3.isEmpty(dataSourceTemplateCut)) {
            log.error("elasticSearch切面异常");
            throw new BadElasticSearchException("038", "elasticSearch切面异常");
        }

        ElasticSearchRouting elasticSearchRouting = new ElasticSearchRouting();
        elasticSearchRouting.routing(properties, dataSourceTemplateCut);
        return elasticSearchRouting;

    }


    /**
     * Returns the base packages to scan for Elasticsearch mapped entities at startup. Will return the package name of the
     * configuration class' (the concrete class, not this one here) by default. So if you have a
     * {@code com.acme.AppConfig} extending {@link ElasticsearchConfigurationSupport} the base package will be considered
     * {@code com.acme} unless the method is overridden to implement alternate behavior.
     *
     * @return the base packages to scan for mapped {@link Document} classes or an empty collection to not enable scanning
     * for entities.
     */
    protected Collection<String> getMappingBasePackages() {
        Package mappingBasePackage = getClass().getPackage();
        return Collections.singleton(mappingBasePackage == null ? null : mappingBasePackage.getName());
    }

    /**
     * Scans the mapping base package for classes annotated with {@link Document}. By default, it scans for entities in
     * all packages returned by {@link #getMappingBasePackages()}.
     *
     * @return never {@literal null}.
     * @see #getMappingBasePackages()
     */
    protected Set<Class<?>> getInitialEntitySet() {

        Set<Class<?>> initialEntitySet = new HashSet<>();

        for (String basePackage : getMappingBasePackages()) {
            initialEntitySet.addAll(scanForEntities(basePackage));
        }

        return initialEntitySet;
    }

    /**
     * Scans the given base package for entities, i.e. Elasticsearch specific types annotated with {@link Document} and
     * {@link Persistent}.
     *
     * @param basePackage must not be {@literal null}.
     * @return never {@literal null}.
     */
    protected Set<Class<?>> scanForEntities(String basePackage) {

        if (!StringUtils.hasText(basePackage)) {
            return Collections.emptySet();
        }

        Set<Class<?>> initialEntitySet = new HashSet<>();

        ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
                false);
        componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
        componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));

        for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {

            String beanClassName = candidate.getBeanClassName();

            if (beanClassName != null) {
                try {
                    initialEntitySet.add(
                            ClassUtils.forName(beanClassName, AbstractReactiveElasticsearchConfiguration.class.getClassLoader()));
                } catch (ClassNotFoundException | LinkageError ignored) {
                }
            }
        }

        return initialEntitySet;
    }

}
